﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  internal class SkipPagingEnumerator<T> : PagingEnumerator<T>
  {
    #region Static Fields

    private static readonly Guid JumpMoveAndFetchCallId = DataUtils.CreateCallId();

    #endregion

    #region Constructor

    internal SkipPagingEnumerator(
      IAsyncEnumerator childEnumerator,
      Func<int> pageSize )
      : base( childEnumerator, pageSize )
    {
    }

    #endregion

    public override JumpMoveAndFetchAsyncResult BeginJumpMoveAndFetch(
      JumpMoveAndFetchParameters parameters,
      AsyncCallback callback,
      object state )
    {
      this.CheckMoveAndFetchParameters( parameters.ExpectedMoveCount, parameters.ExpectedFetchCount );

      bool isFirstPage = true;
      List<T> currentItems = new List<T>();
      EstimateInt64 totalItemsCount = null;
      Nullable<long> actualMoveCount = null;
      int remainingItemsCount = parameters.ExpectedFetchCount;

      // This anonymous method is fetching a new page.
      Func<AsyncCallback, object, IAsyncResult> getPage = null;
      getPage =
        ( AsyncCallback cb, object st ) =>
        {
          int nextFetchCount = this.GetFetchCount( remainingItemsCount );
          JumpMoveAndFetchParameters newParameters;

          // We must apply the expected move on the first page we fetch.
          if( isFirstPage )
          {
            isFirstPage = false;
            newParameters = parameters.CreateWithNewFetchCount( nextFetchCount );
          }
          // We use the last query result to get the next page.
          else
          {
            SourcePosition startingPosition = new ItemPosition( currentItems.Last() );

            newParameters = new JumpMoveAndFetchParameters(
                                  startingPosition,
                                  DataUtils.GetUnit( nextFetchCount ),
                                  nextFetchCount,
                                  parameters.ApproximateMove,
                                  parameters.Context );
          }

          return base.BeginJumpMoveAndFetch( newParameters, cb, st );
        };

      // This anonymous method is handling a page and ask for a new page if necessary.
      Func<AsyncResult, IAsyncResult, DependentCallResult> getNextPage = null;
      getNextPage =
        ( AsyncResult par, IAsyncResult iar ) =>
        {
          JumpMoveAndFetchAsyncResult parentAsyncResult = ( JumpMoveAndFetchAsyncResult )par;
          JumpMoveAndFetchResult<T> pageResult = base.EndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )iar );

          if( ( m_disposed ) || ( pageResult == null ) )
            return new DependentCallResult( null );

          // Set/Refresh the total items count.
          if( pageResult.TotalCount != null )
          {
            totalItemsCount = pageResult.TotalCount;
          }
          // The total items count we had previously is unreliable.
          else if( totalItemsCount != null )
          {
            totalItemsCount = totalItemsCount.ToEstimate();
          }

          if( !actualMoveCount.HasValue )
          {
            actualMoveCount = pageResult.ActualMoveCount;
          }

          // Append the fetched items to the final result.
          if( pageResult.FetchedItems != null )
          {
            ICollection<T> fetchedItems = pageResult.FetchedItems;

            currentItems.AddRange( fetchedItems );
            remainingItemsCount -= fetchedItems.Count * pageResult.GetFetchUnit();
          }

          // We do not have to fetch anymore page.
          if( ( !pageResult.Result ) || ( remainingItemsCount == 0 ) )
          {
            bool success = ( pageResult.Result ) && ( remainingItemsCount == 0 );
            ICollection<T> fetchedItems = ( success || currentItems.Count > 0 )
                                            ? currentItems
                                            : null;

            return new DependentCallResult(
                     new JumpMoveAndFetchResult<T>(
                       parentAsyncResult,
                       actualMoveCount,
                       fetchedItems,
                       success,
                       pageResult.EndPositionObject,
                       pageResult.EndPositionOffset,
                       totalItemsCount ) );
          }
          // Some items are still missing, we'll fetch another page.
          else
          {
            return new DependentCallResult( null, getPage, getNextPage );
          }
        };

      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult(
                                                  SkipPagingEnumerator<T>.JumpMoveAndFetchCallId,
                                                  callback,
                                                  state,
                                                  parameters );

      asyncResult.Start( getPage, getNextPage );

      return asyncResult;
    }

    public override JumpMoveAndFetchResult<T> EndJumpMoveAndFetch(
      JumpMoveAndFetchAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( SkipPagingEnumerator<T>.JumpMoveAndFetchCallId );

      return asyncResult.GetCallResult<JumpMoveAndFetchResult<T>>();
    }

    protected override void Dispose( bool disposing )
    {
      m_disposed = true;

      base.Dispose( disposing );
    }

    #region Private Fields

    private bool m_disposed; //false

    #endregion
  }
}
